//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================


namespace Shp.Runtime.Sitka.ServiceAgent.CrossTenant
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Collections.ObjectModel;

    
    public class CrossTenantSearch<T>
    {
        public delegate IList<T> SearchHandler(string tenantId, string query);
 
        /// <summary>
        /// Searches for entities of the same type (T) across multiple tenants. The search statement (SLINQ) is the same for all.
        /// </summary>
        /// <param name="tenantIds">The list of tenants to search on</param>
        /// <param name="searchHandler">A delegate that will handle the query itself</param>
        /// <param name="query">The Query statement</param>
        /// <returns>A List of T's</returns>
        public IList<T> Search(string[] tenantIds, SearchHandler searchHandler, string query)
        {
            ///IN this implementation, there can only be 64 outstanding requests at any given time.
            if (tenantIds.Count() > 64)
            {
                throw new ArgumentOutOfRangeException("tenantIds", "The maximum number of tenants to search on is 64 in this implemenation.");
            }

            AutoResetEvent[] handles = new AutoResetEvent[tenantIds.Count()];
            SearchState[] states = new SearchState[tenantIds.Count()];

            for (int i = 0; i < tenantIds.Count(); i++)
            {
                states[i] = new SearchState();
                states[i].AutoResetHandler = handles[i] = new AutoResetEvent(false);
                states[i].Where = query;
                states[i].TenantId = tenantIds[i];
                states[i].SearchOperationHandler = searchHandler;
                ThreadPool.QueueUserWorkItem(new WaitCallback(InnerSearchWorker), states[i]);
            }

            //Wait on all threads to finalize (that's why we have a64 query limit, WaitAll only supports 64 handles)
            WaitHandle.WaitAll(handles);

            List<T> results = new List<T>();

            // Really simple merge of all results
            for (int x = 0; x < tenantIds.Count(); x++)
            {
                Exception ex = states[x].SearchException;

                if( ex != null )
                {
                    throw new InvalidOperationException( "There was at least 1 search exception", ex );
                }

                if ( states[x].PartialResults != null )
                {
                    results.AddRange(states[x].PartialResults);
                }
            }

            return results;
        }

        /// <summary>
        /// Search Worker for each Tenant.
        /// </summary>
        /// <param name="State"></param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private static void InnerSearchWorker(object state)
        {
            SearchState st = (SearchState)state;

            try
            {
                st.PartialResults = st.SearchOperationHandler(st.TenantId, st.Where);
            }
            catch (Exception ex)
            {
                st.SearchException = ex;
            }
            finally
            {
                st.AutoResetHandler.Set();
            }
        }

        internal class SearchState
        {
            public string Where { get; set; }
            public AutoResetEvent AutoResetHandler { get; set; }
            public IList<T> PartialResults { get; set; }
            public string TenantId { get; set; }
            public Exception SearchException { get; set; }
            public SearchHandler SearchOperationHandler { get; set; }
        }
    }
}
